/*
 * jQuery UI Slider
 *
 * Copyright (c) 2008 Paul Bakaus
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 *
 * http://docs.jquery.com/UI/Slider
 *
 * Depends:
 *	ui.core.js
 */
(function($) {

    $.fn.unwrap = $.fn.unwrap || function(expr) {
        return this.each(function() {
            $(this).parents(expr).eq(0).after(this).remove();
        });
    };

    $.widget("ui.slider", {
        plugins: {},
        ui: function(e) {
            return {
                options: this.options,
                handle: this.currentHandle,
                value: this.options.axis != "both" || !this.options.axis ? Math.round(this.value(null, this.options.axis == "vertical" ? "y" : "x")) : {
                    x: Math.round(this.value(null, "x")),
                    y: Math.round(this.value(null, "y"))
                },
                range: this.getRange()
            };
        },
        propagate: function(n, e) {
            $.ui.plugin.call(this, n, [e, this.ui()]);
            this.element.triggerHandler(n == "slide" ? n : "slide" + n, [e, this.ui()], this.options[n]);
        },
        destroy: function() {

            this.element
                    .removeClass("ui-slider ui-slider-disabled")
                    .removeData("slider")
                    .unbind(".slider");

            if (this.handle && this.handle.length) {
                this.handle
                        .unwrap("a");
                this.handle.each(function() {
                    $(this).data("mouse").mouseDestroy();
                });
            }

            this.generated && this.generated.remove();

        },
        setData: function(key, value) {
            $.widget.prototype.setData.apply(this, arguments);
            if (/min|max|steps/.test(key)) {
                this.initBoundaries();
            }

            if (key == "range") {
                value ? this.handle.length == 2 && this.createRange() : this.removeRange();
            }

        },

        init: function() {

            var self = this;
            this.element.addClass("ui-slider");
            this.initBoundaries();

            // Initialize mouse and key events for interaction
            this.handle = $(this.options.handle, this.element);
            if (!this.handle.length) {
                self.handle = self.generated = $(self.options.handles || [0]).map(function() {
                    var handle = $("<div/>").addClass("ui-slider-handle").appendTo(self.element);
                    if (this.id)
                        handle.attr("id", this.id);
                    return handle[0];
                });
            }


            var handleclass = function(el) {
                this.element = $(el);
                this.element.data("mouse", this);
                this.options = self.options;

                this.element.bind("mousedown", function() {
                    if (self.currentHandle) this.blur(self.currentHandle);
                    self.focus(this, 1);
                });

                this.mouseInit();
            };

            $.extend(handleclass.prototype, $.ui.mouse, {
                mouseStart: function(e) {
                    return self.start.call(self, e, this.element[0]);
                },
                mouseStop: function(e) {
                    return self.stop.call(self, e, this.element[0]);
                },
                mouseDrag: function(e) {
                    return self.drag.call(self, e, this.element[0]);
                },
                mouseCapture: function() {
                    return true;
                },
                trigger: function(e) {
                    this.mouseDown(e);
                }
            });


            $(this.handle)
                    .each(function() {
                new handleclass(this);
            })
                    .wrap('<a href="javascript:void(0)" style="outline:none;border:none;"></a>')
                    .parent()
                    .bind('focus', function(e) {
                self.focus(this.firstChild);
            })
                    .bind('blur', function(e) {
                self.blur(this.firstChild);
            })
                    .bind('keydown', function(e) {
                if (!self.options.noKeyboard) self.keydown(e.keyCode, this.firstChild);
            })
                    ;

            // Bind the click to the slider itself
            this.element.bind('mousedown.slider', function(e) {
                self.click.apply(self, [e]);
                self.currentHandle.data("mouse").trigger(e);
                self.firstValue = self.firstValue + 1; //This is for always triggering the change event
            });

            // Move the first handle to the startValue
            $.each(this.options.handles || [], function(index, handle) {
                self.moveTo(handle.start, index, true);
            });
            if (!isNaN(this.options.startValue))
                this.moveTo(this.options.startValue, 0, true);

            this.previousHandle = $(this.handle[0]); //set the previous handle to the first to allow clicking before selecting the handle
            if (this.handle.length == 2 && this.options.range) this.createRange();
        },
        initBoundaries: function() {

            var element = this.element[0], o = this.options;
            this.actualSize = { width: this.element.outerWidth() , height: this.element.outerHeight() };

            $.extend(o, {
                axis: o.axis || (element.offsetWidth < element.offsetHeight ? 'vertical' : 'horizontal'),
                max: !isNaN(parseInt(o.max, 10)) ? { x: parseInt(o.max, 10), y: parseInt(o.max, 10) } : ({ x: o.max && o.max.x || 100, y: o.max && o.max.y || 100 }),
                min: !isNaN(parseInt(o.min, 10)) ? { x: parseInt(o.min, 10), y: parseInt(o.min, 10) } : ({ x: o.min && o.min.x || 0, y: o.min && o.min.y || 0 })
            });
            //Prepare the real maxValue
            o.realMax = {
                x: o.max.x - o.min.x,
                y: o.max.y - o.min.y
            };
            //Calculate stepping based on steps
            o.stepping = {
                x: o.stepping && o.stepping.x || parseInt(o.stepping, 10) || (o.steps ? o.realMax.x / (o.steps.x || parseInt(o.steps, 10) || o.realMax.x) : 0),
                y: o.stepping && o.stepping.y || parseInt(o.stepping, 10) || (o.steps ? o.realMax.y / (o.steps.y || parseInt(o.steps, 10) || o.realMax.y) : 0)
            };
        },


        keydown: function(keyCode, handle) {
            if (/(37|38|39|40)/.test(keyCode)) {
                this.moveTo({
                    x: /(37|39)/.test(keyCode) ? (keyCode == 37 ? '-' : '+') + '=' + this.oneStep("x") : 0,
                    y: /(38|40)/.test(keyCode) ? (keyCode == 38 ? '-' : '+') + '=' + this.oneStep("y") : 0
                }, handle);
            }
        },
        focus: function(handle, hard) {
            this.currentHandle = $(handle).addClass('ui-slider-handle-active');
            if (hard)
                this.currentHandle.parent()[0].focus();
        },
        blur: function(handle) {
            $(handle).removeClass('ui-slider-handle-active');
            if (this.currentHandle && this.currentHandle[0] == handle) {
                this.previousHandle = this.currentHandle;
                this.currentHandle = null;
            }
            ;
        },
        click: function(e) {
            // This method is only used if:
            // - The user didn't click a handle
            // - The Slider is not disabled
            // - There is a current, or previous selected handle (otherwise we wouldn't know which one to move)

            var pointer = [e.pageX,e.pageY];

            var clickedHandle = false;
            this.handle.each(function() {
                if (this == e.target)
                    clickedHandle = true;
            });
            if (clickedHandle || this.options.disabled || !(this.currentHandle || this.previousHandle))
                return;

            // If a previous handle was focussed, focus it again
            if (!this.currentHandle && this.previousHandle)
                this.focus(this.previousHandle, true);

            // propagate only for distance > 0, otherwise propagation is done my drag
            this.offset = this.element.offset();

            this.moveTo({
                y: this.convertValue(e.pageY - this.offset.top - this.currentHandle[0].offsetHeight / 2, "y"),
                x: this.convertValue(e.pageX - this.offset.left - this.currentHandle[0].offsetWidth / 2, "x")
            }, null, !this.options.distance);
        },



        createRange: function() {
            if (this.rangeElement) return;
            this.rangeElement = $('<div></div>')
                    .addClass('ui-slider-range')
                    .css({ position: 'absolute' })
                    .appendTo(this.element);
            this.updateRange();
        },
        removeRange: function() {
            this.rangeElement.remove();
            this.rangeElement = null;
        },
        updateRange: function() {
            var prop = this.options.axis == "vertical" ? "top" : "left";
            var size = this.options.axis == "vertical" ? "height" : "width";
            this.rangeElement.css(prop, (parseInt($(this.handle[0]).css(prop), 10) || 0) + this.handleSize(0, this.options.axis == "vertical" ? "y" : "x") / 2);
            this.rangeElement.css(size, (parseInt($(this.handle[1]).css(prop), 10) || 0) - (parseInt($(this.handle[0]).css(prop), 10) || 0));
        },
        getRange: function() {
            return this.rangeElement ? this.convertValue(parseInt(this.rangeElement.css(this.options.axis == "vertical" ? "height" : "width"), 10), this.options.axis == "vertical" ? "y" : "x") : null;
        },

        handleIndex: function() {
            return this.handle.index(this.currentHandle[0]);
        },
        value: function(handle, axis) {
            if (this.handle.length == 1) this.currentHandle = this.handle;
            if (!axis) axis = this.options.axis == "vertical" ? "y" : "x";

            var curHandle = $(handle != undefined && handle !== null ? this.handle[handle] || handle : this.currentHandle);

            if (curHandle.data("mouse").sliderValue) {
                return parseInt(curHandle.data("mouse").sliderValue[axis], 10);
            } else {
                return parseInt(((parseInt(curHandle.css(axis == "x" ? "left" : "top"), 10) / (this.actualSize[axis == "x" ? "width" : "height"] - this.handleSize(handle, axis))) * this.options.realMax[axis]) + this.options.min[axis], 10);
            }

        },
        convertValue: function(value, axis) {
            return this.options.min[axis] + (value / (this.actualSize[axis == "x" ? "width" : "height"] - this.handleSize(null, axis))) * this.options.realMax[axis];
        },

        translateValue: function(value, axis) {
            return ((value - this.options.min[axis]) / this.options.realMax[axis]) * (this.actualSize[axis == "x" ? "width" : "height"] - this.handleSize(null, axis));
        },
        translateRange: function(value, axis) {
            if (this.rangeElement) {
                if (this.currentHandle[0] == this.handle[0] && value >= this.translateValue(this.value(1), axis))
                    value = this.translateValue(this.value(1, axis) - this.oneStep(axis), axis);
                if (this.currentHandle[0] == this.handle[1] && value <= this.translateValue(this.value(0), axis))
                    value = this.translateValue(this.value(0, axis) + this.oneStep(axis), axis);
            }
            if (this.options.handles) {
                var handle = this.options.handles[this.handleIndex()];
                if (value < this.translateValue(handle.min, axis)) {
                    value = this.translateValue(handle.min, axis);
                } else if (value > this.translateValue(handle.max, axis)) {
                    value = this.translateValue(handle.max, axis);
                }
            }
            return value;
        },
        translateLimits: function(value, axis) {
            if (value >= this.actualSize[axis == "x" ? "width" : "height"] - this.handleSize(null, axis))
                value = this.actualSize[axis == "x" ? "width" : "height"] - this.handleSize(null, axis);
            if (value <= 0)
                value = 0;
            return value;
        },
        handleSize: function(handle, axis) {
            return $(handle != undefined && handle !== null ? this.handle[handle] : this.currentHandle)[0]["offset" + (axis == "x" ? "Width" : "Height")];
        },
        oneStep: function(axis) {
            return this.options.stepping[axis] || 1;
        },


        start: function(e, handle) {

            var o = this.options;
            if (o.disabled) return false;

            // Prepare the outer size
            this.actualSize = { width: this.element.outerWidth() , height: this.element.outerHeight() };

            // This is a especially ugly fix for strange blur events happening on mousemove events
            if (!this.currentHandle)
                this.focus(this.previousHandle, true);

            this.offset = this.element.offset();

            this.handleOffset = this.currentHandle.offset();
            this.clickOffset = { top: e.pageY - this.handleOffset.top, left: e.pageX - this.handleOffset.left };

            this.firstValue = this.value();

            this.propagate('start', e);
            this.drag(e, handle);
            return true;

        },
        stop: function(e) {
            this.propagate('stop', e);
            if (this.firstValue != this.value())
                this.propagate('change', e);
            // This is a especially ugly fix for strange blur events happening on mousemove events
            this.focus(this.currentHandle, true);
            return false;
        },
        drag: function(e, handle) {

            var o = this.options;
            var position = { top: e.pageY - this.offset.top - this.clickOffset.top, left: e.pageX - this.offset.left - this.clickOffset.left};
            if (!this.currentHandle) this.focus(this.previousHandle, true); //This is a especially ugly fix for strange blur events happening on mousemove events

            position.left = this.translateLimits(position.left, "x");
            position.top = this.translateLimits(position.top, "y");

            if (o.stepping.x) {
                var value = this.convertValue(position.left, "x");
                value = Math.round(value / o.stepping.x) * o.stepping.x;
                position.left = this.translateValue(value, "x");
            }
            if (o.stepping.y) {
                var value = this.convertValue(position.top, "y");
                value = Math.round(value / o.stepping.y) * o.stepping.y;
                position.top = this.translateValue(value, "y");
            }

            position.left = this.translateRange(position.left, "x");
            position.top = this.translateRange(position.top, "y");

            if (o.axis != "vertical") this.currentHandle.css({ left: position.left });
            if (o.axis != "horizontal") this.currentHandle.css({ top: position.top });

            //Store the slider's value
            this.currentHandle.data("mouse").sliderValue = {
                x: Math.round(this.convertValue(position.left, "x")) || 0,
                y: Math.round(this.convertValue(position.top, "y")) || 0
            };

            if (this.rangeElement)
                this.updateRange();
            this.propagate('slide', e);
            return false;
        },

        moveTo: function(value, handle, noPropagation) {

            var o = this.options;

            // Prepare the outer size
            this.actualSize = { width: this.element.outerWidth() , height: this.element.outerHeight() };

            //If no handle has been passed, no current handle is available and we have multiple handles, return false
            if (handle == undefined && !this.currentHandle && this.handle.length != 1)
                return false;

            //If only one handle is available, use it
            if (handle == undefined && !this.currentHandle)
                handle = 0;

            if (handle != undefined)
                this.currentHandle = this.previousHandle = $(this.handle[handle] || handle);


            if (value.x !== undefined && value.y !== undefined) {
                var x = value.x, y = value.y;
            } else {
                var x = value, y = value;
            }

            if (x !== undefined && x.constructor != Number) {
                var me = /^\-\=/.test(x), pe = /^\+\=/.test(x);
                if (me || pe) {
                    x = this.value(null, "x") + parseInt(x.replace(me ? '=' : '+=', ''), 10);
                } else {
                    x = isNaN(parseInt(x, 10)) ? undefined : parseInt(x, 10);
                }
            }

            if (y !== undefined && y.constructor != Number) {
                var me = /^\-\=/.test(y), pe = /^\+\=/.test(y);
                if (me || pe) {
                    y = this.value(null, "y") + parseInt(y.replace(me ? '=' : '+=', ''), 10);
                } else {
                    y = isNaN(parseInt(y, 10)) ? undefined : parseInt(y, 10);
                }
            }

            if (o.axis != "vertical" && x !== undefined) {
                if (o.stepping.x) x = Math.round(x / o.stepping.x) * o.stepping.x;
                x = this.translateValue(x, "x");
                x = this.translateLimits(x, "x");
                x = this.translateRange(x, "x");

                o.animate ? this.currentHandle.stop().animate({ left: x }, (Math.abs(parseInt(this.currentHandle.css("left")) - x)) * (!isNaN(parseInt(o.animate)) ? o.animate : 5)) : this.currentHandle.css({ left: x });
            }

            if (o.axis != "horizontal" && y !== undefined) {
                if (o.stepping.y) y = Math.round(y / o.stepping.y) * o.stepping.y;
                y = this.translateValue(y, "y");
                y = this.translateLimits(y, "y");
                y = this.translateRange(y, "y");
                o.animate ? this.currentHandle.stop().animate({ top: y }, (Math.abs(parseInt(this.currentHandle.css("top")) - y)) * (!isNaN(parseInt(o.animate)) ? o.animate : 5)) : this.currentHandle.css({ top: y });
            }

            if (this.rangeElement)
                this.updateRange();

            //Store the slider's value
            this.currentHandle.data("mouse").sliderValue = {
                x: Math.round(this.convertValue(x, "x")) || 0,
                y: Math.round(this.convertValue(y, "y")) || 0
            };

            if (!noPropagation) {
                this.propagate('start', null);
                this.propagate('stop', null);
                this.propagate('change', null);
                this.propagate("slide", null);
            }
        }
    });

    $.ui.slider.getter = "value";

    $.ui.slider.defaults = {
        handle: ".ui-slider-handle",
        distance: 1,
        animate: false
    };

})(jQuery);
